home *** CD-ROM | disk | FTP | other *** search
/ Complete Linux / Complete Linux.iso / docs / system / admin / su1_tar.z / su1_tar / su1 / su1.c < prev   
Encoding:
C/C++ Source or Header  |  1993-04-16  |  15.1 KB  |  894 lines

  1. /*
  2.  * Copyright (c) 1993 by David I. Bell
  3.  * Permission is granted to use, distribute, or modify this source,
  4.  * provided that this copyright notice remains intact.  This program
  5.  * is provided "as is", and I accept no responsibility for security
  6.  * problems either inherent in the program or from its actual use.
  7.  *
  8.  * Program to allow specified users to execute specified commands as root.
  9.  */
  10.  
  11. #include <stdio.h>
  12. #include <sys/types.h>
  13. #include <sys/stat.h>
  14. #include <pwd.h>
  15. #include <unistd.h>
  16. #include <time.h>
  17.  
  18.  
  19. #define    PRIVFILE    "/etc/su1.priv"
  20. #define    INITLOGFILE    "/etc/su1.log"
  21. #define    INITASK        key_always
  22.  
  23. #define    ROOTNAME    "root"
  24. #define    ROOTUID        0
  25. #define    ROOTGID        0
  26. #define    COMMENTCHAR    '#'
  27. #define    WILDSTR        "*"
  28.  
  29. #define    MAXARGS        10000
  30. #define    MAXDEFINES    1000
  31. #define    MAXPATHSIZE    1024
  32.  
  33. typedef    int        BOOL;
  34. #define    FALSE        ((BOOL) 0)
  35. #define    TRUE        ((BOOL) 1)
  36.  
  37.  
  38. #define    isblank(ch)    (((ch) == ' ') || ((ch) == '\t'))
  39.  
  40.  
  41. enum    keyword    {
  42.     key_null, key_define, key_paths, key_logfile, key_password,
  43.     key_ask, key_always, key_never, key_allow, key_refuse,
  44.     key_exact, key_prefix, key_any
  45. };
  46.  
  47.  
  48. struct    keytable {
  49.     char        *name;
  50.     enum keyword    code;
  51. }    keytable[] = {
  52.     "define",    key_define,
  53.     "paths",    key_paths,
  54.     "logfile",    key_logfile,
  55.     "password",    key_password,
  56.     "ask",        key_ask,
  57.     "always",    key_always,
  58.     "never",    key_never,
  59.     "allow",    key_allow,
  60.     "refuse",    key_refuse,
  61.     "exact",    key_exact,
  62.     "prefix",    key_prefix,
  63.     "any",        key_any,
  64.     NULL,        key_null
  65. };
  66.  
  67.  
  68. static    char    *initpaths[] = {
  69.     "/bin",
  70.     "/usr/bin",
  71.     "/etc"
  72. };
  73.  
  74.  
  75. static    char    *privfile = PRIVFILE;
  76. static    enum keyword    ask = INITASK;
  77. static    char    *password = NULL;
  78. static    char    *logfile = NULL;
  79. static    FILE    *privfp = NULL;
  80. static    char    *myname;
  81. static    char    *paths;
  82. static    int    line;
  83. static    int    cmdargc;
  84. static    char    **cmdargv;
  85. static    char    *defines[MAXDEFINES];
  86.  
  87.  
  88. static    char *        makelist();
  89. static    char *        findlist();
  90. static    char *        makestring();
  91. static    enum keyword    findkeyword();
  92. static    BOOL        readline();
  93. static    BOOL        checkcommand();
  94. static    BOOL        checkusers();
  95. static    void        checkmyname();
  96. static    void        checkpassword();
  97. static    void        writelogfile();
  98. static    void        executecommand();
  99. static    void        readprivfile();
  100. static    void        examineline();
  101. static    void        badfile();
  102. static    void        parse_define();
  103. static    void        parse_paths();
  104. static    void        parse_logfile();
  105. static    void        parse_password();
  106. static    void        parse_ask();
  107. static    void        parse_allow();
  108. static    void        parse_refuse();
  109.  
  110. extern    char *        malloc();
  111.  
  112.  
  113. main(argc, argv)
  114.     char    **argv;
  115. {
  116.     if (argc <= 1) {
  117.         fprintf(stderr, "usage: su1 command\n");
  118.         exit(1);
  119.     }
  120.  
  121.     if (geteuid() != ROOTUID) {
  122.         fprintf(stderr, "Not running as root\n");
  123.         exit(1);
  124.     }
  125.  
  126.     cmdargc = argc - 1;
  127.     cmdargv = argv + 1;
  128.  
  129.     if (cmdargc > MAXARGS - 2) {
  130.         fprintf(stderr, "Too many arguments in command\n");
  131.         exit(1);
  132.     }
  133.  
  134.     logfile = makestring(INITLOGFILE);
  135.  
  136.     paths = makelist(sizeof(initpaths) / sizeof(initpaths[0]), initpaths);
  137.  
  138.     checkmyname();
  139.  
  140.     readprivfile();
  141.  
  142.     fprintf(stderr, "Permission denied for command\n");
  143.     exit(1);
  144. }
  145.  
  146.  
  147. /*
  148.  * Read the privilege file.  If we find that the desired command can
  149.  * be executed, we do that and so never return.  If the command is
  150.  * not found, we will return from here.
  151.  */
  152. static void
  153. readprivfile()
  154. {
  155.     char        *cp;
  156.     int        argc;
  157.     char        *argv[MAXARGS];
  158.     char        buf[1024*10];
  159.     struct    stat    statbuf;
  160.  
  161.     privfp = fopen(privfile, "r");
  162.     if (privfp == NULL) {
  163.         perror(privfile);
  164.         exit(1);
  165.     }
  166.  
  167.     if (fstat(fileno(privfp), &statbuf)) {
  168.         fprintf(stderr, "Cannot stat privilege file\n");
  169.         exit(1);
  170.     }
  171.  
  172.     if ((statbuf.st_uid != ROOTUID) || (statbuf.st_mode & 022)) {
  173.         fprintf(stderr, "Privilege file is not secure\n");
  174.         exit(1);
  175.     }
  176.  
  177.     while (readline(buf, sizeof(buf))) {
  178.         cp = buf;
  179.         while (isblank(*cp))
  180.             cp++;
  181.  
  182.         if (*cp == COMMENTCHAR)
  183.             continue;
  184.  
  185.         argc = 0;
  186.         while (*cp) {
  187.             if (argc >= MAXARGS)
  188.                 badfile("too many arguments");
  189.  
  190.             argv[argc++] = cp;
  191.  
  192.             while (*cp && !isblank(*cp))
  193.                 cp++;
  194.  
  195.             while (isblank(*cp))
  196.                 *cp++ = '\0';
  197.         }
  198.         examineline(argc, argv);
  199.     }
  200.  
  201.     fclose(privfp);
  202. }
  203.  
  204.  
  205. /*
  206.  * Read a line of the privilege file, taking into account continuation
  207.  * characters.  Returns TRUE if a line was read.
  208.  */
  209. static BOOL
  210. readline(buf, buflen)
  211.     char    *buf;
  212. {
  213.     char    *cp;
  214.     BOOL    gotdata;
  215.  
  216.     buflen--;
  217.     gotdata = FALSE;
  218.  
  219.     while (fgets(buf, buflen, privfp)) {
  220.         gotdata = TRUE;
  221.         line++;
  222.  
  223.         cp = buf + strlen(buf);
  224.         if (cp == buf)
  225.             badfile("missing newline character");
  226.  
  227.         cp--;
  228.         if (*cp != '\n')
  229.             badfile("line too long");
  230.         *cp = '\0';
  231.  
  232.         if (cp == buf)
  233.             return TRUE;
  234.  
  235.         cp--;
  236.         if (*cp != '\\')
  237.             return TRUE;
  238.  
  239.         *cp++ = ' ';
  240.         buflen -= (cp - buf);
  241.         buf = cp;
  242.     }
  243.  
  244.     if (ferror(privfp))
  245.         badfile("read error");
  246.  
  247.     if (gotdata)
  248.         badfile("premature end of file");
  249.  
  250.     return FALSE;
  251. }
  252.  
  253.  
  254. /*
  255.  * Examine one line of the privilege file.
  256.  */
  257. static void
  258. examineline(argc, argv)
  259.     char    **argv;
  260. {
  261.     if (argc-- <= 0)
  262.         return;
  263.  
  264.     switch (findkeyword(*argv++)) {
  265.         case key_define:
  266.             parse_define(argc, argv);
  267.             break;
  268.  
  269.         case key_paths:
  270.             parse_paths(argc, argv);
  271.             break;
  272.  
  273.         case key_logfile:
  274.             parse_logfile(argc, argv);
  275.             break;
  276.  
  277.         case key_password:
  278.             parse_password(argc, argv);
  279.             break;
  280.  
  281.         case key_ask:
  282.             parse_ask(argc, argv);
  283.             break;
  284.  
  285.         case key_allow:
  286.             parse_allow(argc, argv);
  287.             break;
  288.  
  289.         case key_refuse:
  290.             parse_refuse(argc, argv);
  291.             break;
  292.  
  293.         default:
  294.             badfile("unknown keyword at beginning of line");
  295.     }
  296. }
  297.  
  298.  
  299. static void
  300. parse_define(argc, argv)
  301.     char    **argv;
  302. {
  303.     int    i;
  304.     char    *str;
  305.  
  306.     if (argc <= 0)
  307.         badfile("missing define name");
  308.  
  309.     str = makelist(argc, argv);
  310.  
  311.     for (i = 0; i < MAXDEFINES; i++) {
  312.         if (defines[i] == NULL) {
  313.             defines[i] = str;
  314.             return;
  315.         }
  316.  
  317.         if (strcmp(defines[i], str) == 0) {
  318.             free(defines[i]);
  319.             defines[i] = str;
  320.             return;
  321.         }
  322.     }
  323.     badfile("too many define strings");
  324. }
  325.  
  326.  
  327. static void
  328. parse_paths(argc, argv)
  329.     char    **argv;
  330. {
  331.     int    ac;
  332.     char    **av;
  333.  
  334.     if (argc <= 0)
  335.         badfile("no paths specified");
  336.  
  337.     ac = argc;
  338.     av = argv;
  339.     while (ac-- > 0) {
  340.         if (**av++ != '/')
  341.             badfile("path names must be absolute");
  342.     }
  343.  
  344.     free(paths);
  345.     paths = makelist(argc, argv);
  346. }
  347.  
  348.  
  349. static void
  350. parse_logfile(argc, argv)
  351.     char    **argv;
  352. {
  353.     if (argc <= 0)
  354.         badfile("missing log file path");
  355.  
  356.     if (argc > 1)
  357.         badfile("only one log file path can be specified");
  358.  
  359.     if (**argv != '/')
  360.         badfile("log file path must be absolute");
  361.  
  362.     free(logfile);
  363.     logfile = makestring(*argv);
  364. }
  365.  
  366.  
  367. static void
  368. parse_password(argc, argv)
  369.     char    **argv;
  370. {
  371.     if (password)
  372.         free(password);
  373.     password = NULL;
  374.  
  375.     if (argc <= 0)
  376.         return;
  377.  
  378.     if (argc > 1)
  379.         badfile("only one password can be specified");
  380.  
  381.     if (strlen(*argv) < 3)
  382.         badfile("password must be more than two characters");
  383.  
  384.     password = makestring(*argv);
  385. }
  386.  
  387.  
  388. static void
  389. parse_ask(argc, argv)
  390.     char    **argv;
  391. {
  392.     if (argc <= 0)
  393.         badfile("missing value for ask keyword");
  394.  
  395.     if (argc > 1)
  396.         badfile("multiple values for ask keyword not allowed");
  397.  
  398.     ask = findkeyword(*argv);
  399.  
  400.     switch (ask) {
  401.         case key_never:
  402.         case key_always:
  403.             return;
  404.  
  405.         default:
  406.             badfile("unknown value specified for ask keyword");
  407.     }
  408. }
  409.  
  410.  
  411. static void
  412. parse_allow(argc, argv)
  413.     char    **argv;
  414. {
  415.     char        *user;
  416.     enum    keyword    type;
  417.  
  418.     if (argc < 2)
  419.         badfile("insufficient arguments for allow keyword");
  420.  
  421.     user = *argv++;
  422.     type = findkeyword(*argv++);
  423.     argc -= 2;
  424.  
  425.     switch (type) {
  426.         case key_prefix:
  427.         case key_exact:
  428.             if (!checkcommand(argc, argv, type))
  429.                 return;
  430.             break;
  431.  
  432.         case key_any:
  433.             break;
  434.  
  435.         default:
  436.             badfile("bad command type specified for allow keyword");
  437.     }
  438.  
  439.     if (!checkusers(user))
  440.         return;
  441.  
  442.     /*
  443.      * The user can execute his chosen command.  Ask for and check
  444.      * the password, write the log file entry, and finally execute
  445.      * his command.  We never return from here.
  446.      */
  447.     fclose(privfp);
  448.     checkpassword();
  449.     writelogfile();
  450.     executecommand(type);
  451.     exit(1);
  452. }
  453.  
  454.  
  455. static void
  456. parse_refuse(argc, argv)
  457.     char    **argv;
  458. {
  459.     char        *user;
  460.     enum    keyword    type;
  461.  
  462.     if (argc < 2)
  463.         badfile("insufficient arguments for refuse keyword");
  464.  
  465.     user = *argv++;
  466.     type = findkeyword(*argv++);
  467.     argc -= 2;
  468.  
  469.     switch (type) {
  470.         case key_prefix:
  471.         case key_exact:
  472.             if (!checkcommand(argc, argv, type))
  473.                 return;
  474.             break;
  475.  
  476.         case key_any:
  477.             break;
  478.  
  479.         default:
  480.             badfile("bad command type specified for refuse keyword");
  481.     }
  482.  
  483.     if (!checkusers(user))
  484.         return;
  485.  
  486.     /*
  487.      * The user has explicitly been refused to do this command.
  488.      * Terminate the program immediately with an error.
  489.      */
  490.     fprintf(stderr, "Permission denied for command\n");
  491.     exit(1);
  492. }
  493.  
  494.  
  495. /*
  496.  * Complain about a bad line in the privilege file and exit.
  497.  */
  498. static void
  499. badfile(str)
  500.     char    *str;
  501. {
  502.     fprintf(stderr, "Error in \"%s\", line %d: %s\n",
  503.         privfile, line, str);
  504.     exit(1);
  505. }
  506.  
  507.  
  508. /*
  509.  * Check the specified string to see if it is a keyword.
  510.  * Returns the keyword value if so, or key_null if it was not found.
  511.  */
  512. static enum keyword
  513. findkeyword(str)
  514.     char    *str;
  515. {
  516.     struct    keytable    *key;
  517.  
  518.     for (key = keytable; key->name; key++)
  519.         if (strcmp(str, key->name) == 0)
  520.             return key->code;
  521.  
  522.     return key_null;
  523. }
  524.  
  525.  
  526. /*
  527.  * Append an array of words together to form a long list of strings.
  528.  * The strings are separated by nulls, and the end of the list is
  529.  * marked by an extra null.
  530.  */
  531. char *
  532. makelist(argc, argv)
  533.     char    **argv;
  534. {
  535.     int    len;
  536.     int    ac;
  537.     char    **av;
  538.     char    *str;
  539.     char    *cp;
  540.  
  541.     ac = argc;
  542.     av = argv;
  543.  
  544.     len = 1;
  545.     while (ac-- > 0)
  546.         len += strlen(*av++) + 1;
  547.  
  548.     str = malloc(len);
  549.     if (str == NULL)
  550.         badfile("malloc failed for list");
  551.  
  552.     cp = str;
  553.     while (argc-- > 0) {
  554.         strcpy(cp, *argv++);
  555.         cp += strlen(cp) + 1;
  556.     }
  557.     *cp = '\0';
  558.  
  559.     return str;
  560. }
  561.  
  562.  
  563. /*
  564.  * Make a string into a one-element list.
  565.  */
  566. char *
  567. makestring(str)
  568.     char    *str;
  569. {
  570.     int    len;
  571.     char    *cp;
  572.  
  573.     len = strlen(str);
  574.  
  575.     cp = malloc(len + 2);
  576.     if (cp == NULL)
  577.         badfile("malloc failed");
  578.  
  579.     memcpy(cp, str, len + 1);
  580.     cp[len + 1] = '\0';
  581.  
  582.     return cp;
  583. }
  584.  
  585.  
  586. /*
  587.  * Check some command arguments to see if they match the ones the
  588.  * user is trying to execute.  Returns TRUE if so.
  589.  */
  590. static BOOL
  591. checkcommand(argc, argv, type)
  592.     char    **argv;
  593.     enum    keyword    type;
  594. {
  595.     char    **hisargv;
  596.  
  597.     if (argc <= 0)
  598.         badfile("missing command for user keyword");
  599.  
  600.     hisargv = cmdargv;
  601.  
  602.     if (cmdargc < argc)
  603.         return FALSE;
  604.  
  605.     if ((type == key_exact) && (cmdargc != argc))
  606.         return FALSE;
  607.  
  608.     while (argc-- > 0) {
  609.         if (strcmp(*argv++, *hisargv++))
  610.             return FALSE;
  611.     }
  612.  
  613.     return TRUE;
  614. }
  615.  
  616.  
  617. /*
  618.  * Check our user name against a specified user name or list of user names.
  619.  * Returns TRUE if our name is in the list.
  620.  */
  621. static BOOL
  622. checkusers(name)
  623.     char    *name;
  624. {
  625.     char    *str;
  626.  
  627.     str = findlist(name);
  628.     if (str == NULL)
  629.         str = makestring(name);
  630.  
  631.     while (*str) {
  632.         if (strcmp(str, WILDSTR) == 0)
  633.             return TRUE;
  634.  
  635.         if (strcmp(str, myname) == 0)
  636.             return TRUE;
  637.  
  638.         str += strlen(str) + 1;
  639.     }
  640.  
  641.     return FALSE;
  642. }
  643.  
  644.  
  645. /*
  646.  * Search for a definition for the given list name, and return the list.
  647.  * This does not return the initial element of the list (which is the name).
  648.  * If the list name was not found, a NULL pointer is returned.
  649.  */
  650. static char *
  651. findlist(name)
  652.     char    *name;
  653. {
  654.     int    i;
  655.     char    *str;
  656.  
  657.     for (i = 0; i < MAXDEFINES; i++) {
  658.         str = defines[i];
  659.         if ((str == NULL) || strcmp(str, name))
  660.             continue;
  661.  
  662.         str += strlen(str) + 1;
  663.  
  664.         return str;
  665.     }
  666.  
  667.     return NULL;
  668. }
  669.  
  670.  
  671. /*
  672.  * Get my user name and user and group ids, and verify that what we are
  673.  * supposedly named really matches our real ids.  We need to do this since
  674.  * our user name can be compromised.
  675.  */
  676. static void
  677. checkmyname()
  678. {
  679.     struct    passwd    *pwd;
  680.  
  681.     myname = getlogin();
  682.  
  683.     if (myname == NULL)
  684.         myname = cuserid(NULL);
  685.  
  686.     if (myname == NULL) {
  687.         fprintf(stderr, "Cannot find your login name\n");
  688.         exit(1);
  689.     }
  690.  
  691.     pwd = getpwnam(myname);
  692.     if (pwd == NULL) {
  693.         fprintf(stderr, "Cannot find your password entry\n");
  694.         exit(1);
  695.     }
  696.  
  697.     if (pwd->pw_uid != getuid()) {
  698.         fprintf(stderr, "Your user name and uid do not agree\n");
  699.         exit(1);
  700.     }
  701.  
  702.     /*
  703.      * This check isn't done since the group id can vary for a user.
  704.      */
  705. #if 0
  706.     if (pwd->pw_gid != getgid()) {
  707.         fprintf(stderr, "Your user name and gid do not agree\n");
  708.         exit(1);
  709.     }
  710. #endif
  711. }
  712.  
  713.  
  714. /*
  715.  * Ask for the required password before allowing the command to execute.
  716.  * This password can be one given in the privilege file, or else the real
  717.  * root password.
  718.  */
  719. static void
  720. checkpassword()
  721. {
  722.     struct    passwd    *pwd;
  723.     char        *str;
  724.  
  725.     if (ask == key_never)
  726.         return;
  727.  
  728.     if (password == NULL) {
  729.         pwd = getpwnam(ROOTNAME);
  730.         if (pwd == NULL) {
  731.             fprintf(stderr, "Cannot get root password\n");
  732.             exit(1);
  733.         }
  734.         password = pwd->pw_passwd;
  735.     }
  736.  
  737.     if (*password == '\0')
  738.         return;
  739.  
  740.     str = getpass("Password: ");
  741.     str = crypt(str, password);
  742.  
  743.     if (strcmp(str, password)) {
  744.         fprintf(stderr, "Incorrect password\n");
  745.         exit(1);
  746.     }
  747. }
  748.  
  749.  
  750. /*
  751.  * Write out the log file.
  752.  * There is a not-too-important race in this routine for multiple users.
  753.  */
  754. static void
  755. writelogfile()
  756. {
  757.     FILE    *fp;
  758.     int    ac;
  759.     char    **av;
  760.     time_t    timebuf;
  761.  
  762.     if (strcmp(logfile, "/dev/null") == 0)
  763.         return;
  764.  
  765.     fp = fopen(logfile, "a");
  766.     if (fp == NULL) {
  767.         fprintf(stderr, "Cannot open log file\n");
  768.         exit(1);
  769.     }
  770.  
  771.     time(&timebuf);
  772.     fprintf(fp, "%-8s %16.16s ", myname, ctime(&timebuf));
  773.  
  774.     ac = cmdargc;
  775.     av = cmdargv;
  776.     while (ac-- > 0)
  777.         fprintf(fp, " %s", *av++);
  778.  
  779.     fprintf(fp, "\n");
  780.     fflush(fp);
  781.  
  782.     if (ferror(fp)) {
  783.         fclose(fp);
  784.         fprintf(stderr, "Error writing log file\n");
  785.         exit(1);
  786.     }
  787.  
  788.     if (fclose(fp)) {
  789.         fprintf(stderr, "Error closing log file\n");
  790.         exit(1);
  791.     }
  792. }
  793.  
  794.  
  795. /*
  796.  * Execute the user's command after setting our group and user ids.
  797.  * The type argument specifies whether or not the user can execute
  798.  * any command.
  799.  */
  800. static void
  801. executecommand(type)
  802.     enum    keyword    type;
  803. {
  804.     int    i;
  805.     int    j;
  806.     int    cmdlen;
  807.     char    *cp;
  808.     char    *args[MAXARGS];
  809.     char    execpath[MAXPATHSIZE];
  810.  
  811.     if (setgid(ROOTGID))
  812.         perror("cannot set group id");
  813.  
  814.     if (setuid(ROOTUID))
  815.         perror("cannot set user id");
  816.  
  817.     for (i = 0; i < cmdargc; i++)
  818.         args[i] = cmdargv[i];
  819.  
  820.     args[i] = NULL;
  821.  
  822.     /*
  823.      * If the command type allows any command to be executed, then
  824.      * simply do that, using the user's own PATH specification.
  825.      * This is not a security problem, since the user was explicitly
  826.      * allowed to execute any program.
  827.      */
  828.     if (type == key_any) {
  829.         execvp(args[0], args);
  830.         fprintf(stderr, "Failed to execute command\n");
  831.         exit(1);
  832.     }
  833.  
  834.     /*
  835.      * The command is restricted to a limited set of commands run from
  836.      * a limited number of directories.  See if the command name matches
  837.      * one of the define strings.  If so, then replace the command name
  838.      * with the string definition.
  839.      */
  840.     cp = findlist(cmdargv[0]);
  841.     if (cp) {
  842.         i = 0;
  843.         while (*cp) {
  844.             args[i++] = cp;
  845.             cp += strlen(cp) + 1;
  846.         }
  847.  
  848.         for (j = 1; j < cmdargc; j++) {
  849.             if (i >= MAXARGS - 1) {
  850.                 fprintf(stderr, "Too many arguments\n");
  851.                 exit(1);
  852.             }
  853.             args[i++] = cmdargv[j];
  854.         }
  855.         args[i] = NULL;
  856.     }
  857.  
  858.     /*
  859.      * If the command name contains a slash, then make sure that it
  860.      * is an absolute path, and then try to execute that exact program.
  861.      */
  862.     if (strchr(args[0], '/')) {
  863.         if (args[0][0] != '/') {
  864.             fprintf(stderr, "Command is not an absolute pathname\n");
  865.             exit(1);
  866.         }
  867.         execv(args[0], args);
  868.         fprintf(stderr, "Failed to execute command\n");
  869.         exit(1);
  870.     }
  871.  
  872.     /*
  873.      * The command name is a generic name, so search down our own
  874.      * internal path list, trying to execute the program from each path.
  875.      */
  876.     cmdlen = strlen(args[0]);
  877.  
  878.     for (cp = paths; *cp; cp += strlen(cp) + 1) {
  879.         if (strlen(cp) + cmdlen > MAXPATHSIZE - 2)
  880.             badfile("path name of command is too long");
  881.  
  882.         strcpy(execpath, cp);
  883.         strcat(execpath, "/");
  884.         strcat(execpath, args[0]);
  885.  
  886.         execv(execpath, args);
  887.     }
  888.  
  889.     fprintf(stderr, "Failed to execute command\n");
  890.     exit(1);
  891. }
  892.  
  893. /* END CODE */
  894.